/*
 *  * Copyright (C) 2021 Huawei Device Co., Ltd.
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package com.example.smileyrating;

import ohos.agp.components.VelocityDetector;
import ohos.multimodalinput.event.TouchEvent;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class VelocityTracker {
    private Map<Integer, VelocityDetector> mMap = new HashMap<>();

    private int mActivePointerId = -1;

    private boolean needScale = false;

    private float maxVelocity = Float.MAX_VALUE;

    public VelocityTracker() {
        mMap.put(0, obtainVelocityDetector());
    }

    public void addMovement(TouchEvent ev) {
        int index = ev.getIndex();
        int id = ev.getPointerId(index);
        VelocityDetector detector = mMap.get(id);
        if (detector == null) {
            detector = obtainVelocityDetector();
            mMap.put(id, detector);
        }
        addMovement(detector, ev);
        if (ev.getAction() == ev.getAction()) {
            mActivePointerId = id;
        }
    }

    public void calculateCurrentVelocity(int units, float maxVxVelocity, float maxVyVelocity) {
        needScale = false;
        maxVelocity = Float.MAX_VALUE;
        for (VelocityDetector vd : mMap.values()) {
            vd.calculateCurrentVelocity(units, maxVxVelocity, maxVyVelocity);
        }
    }

    public void calculateCurrentVelocity(int units, float maxVelocity) {
        needScale = true;
        maxVelocity = maxVelocity;
        for (VelocityDetector vd : mMap.values()) {
            if (vd != null) {
                vd.calculateCurrentVelocity(units);
            }
        }
    }

    public void calculateCurrentVelocity(int units) {
        needScale = false;
        maxVelocity = Float.MAX_VALUE;
        for (VelocityDetector vd : mMap.values()) {
            vd.calculateCurrentVelocity(units);
        }
    }

    public float getXVelocity() {
        return getXVelocity(mActivePointerId);
    }

    public float getYVelocity() {
        return getYVelocity(mActivePointerId);
    }

    public float getXVelocity(int id) {
        VelocityDetector vd = mMap.get(id);
        if (vd == null) {
            return 0;
        }
        return getXVelocityWithScale(vd);
    }

    public float getYVelocity(int id) {
        VelocityDetector vd = mMap.get(id);
        if (vd == null) {
            return 0;
        }
        return getYVelocityWithScale(vd);
    }

    public void clear() {
        needScale = false;
        maxVelocity = Float.MAX_VALUE;
        for (VelocityDetector vd : mMap.values()) {
            velocityclear(vd);
        }
    }

    public void recycle() {
        clear();
        mMap.clear();
    }

    private VelocityDetector obtainVelocityDetector() {
        VelocityDetector result = null;
        try {
            Method obtain = VelocityDetector.class.getMethod("obtain");
            result = (VelocityDetector) obtain.invoke(null);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return result;
    }

    private void addMovement(VelocityDetector velocityDetector, TouchEvent touchEvent) {
        if (velocityDetector != null) {
            try {
                Method method = VelocityDetector.class.getMethod("addMovement", TouchEvent.class);
                method.invoke(velocityDetector, touchEvent);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    private void velocityclear(VelocityDetector velocityDetector) {
        if (velocityDetector != null) {
            try {
                Method method = VelocityDetector.class.getMethod("clear");
                method.invoke(velocityDetector);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    private float getXVelocityWithScale(VelocityDetector velocityDetector) {
        if (!needScale) {
            return getXVelocity(velocityDetector);
        } else {
            float xVelocity = getXVelocity(velocityDetector);
            float yVelocity = getYVelocity(velocityDetector);
            if (xVelocity * xVelocity + yVelocity * yVelocity > maxVelocity * maxVelocity) {
                xVelocity /= Math.sqrt((xVelocity * xVelocity + yVelocity * yVelocity) / (maxVelocity * maxVelocity));
            }
            return xVelocity;
        }
    }

    private float getYVelocityWithScale(VelocityDetector velocityDetector) {
        if (!needScale) {
            return getYVelocity(velocityDetector);
        } else {
            float xVelocity = getXVelocity(velocityDetector);
            float yVelocity = getYVelocity(velocityDetector);
            if (xVelocity * xVelocity + yVelocity * yVelocity > maxVelocity * maxVelocity) {
                yVelocity /= Math.sqrt((xVelocity * xVelocity + yVelocity * yVelocity) / (maxVelocity * maxVelocity));
            }
            return yVelocity;
        }
    }

    private float getXVelocity(VelocityDetector velocityDetector) {
        float result = 0;
        if (velocityDetector != null) {
            try {
                Method method = VelocityDetector.class.getMethod("getXVelocity");
                result = (float) method.invoke(velocityDetector);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    private float getYVelocity(VelocityDetector velocityDetector) {
        float result = 0;
        if (velocityDetector != null) {
            try {
                Method method = VelocityDetector.class.getMethod("getYVelocity");
                result = (float) method.invoke(velocityDetector);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        return result;
    }
}
